/* $Id: Match.java 39 2009-07-24 22:48:06Z h_m_r@gmx.de $ */
package org.jugger.tournament.model;

import org.jugger.tournament.model.score.ScoreCalculator;

/**
 * @author Sven Hamer
 */
public class Match {

	private final ScoreCalculator scoreCalculator;

	private Team teamA;
	private Team teamB;
	private int pointsA;
	private int pointsB;
	private boolean extraTime;

	private boolean isFinished;

	/**
	 * Creates a new match between teamA and teamB with given points for each team.
	 * The match will be marked as finished if the scoreCalculator does allow it.
	 * @param scoreCalculator
	 * @param teamA
	 * @param teamB
	 * @param pointsA
	 * @param pointsB
	 */
	public Match(ScoreCalculator scoreCalculator, Team teamA, Team teamB, int pointsA, int pointsB, boolean extraTime, boolean isFinished) {
		this(scoreCalculator, teamA, teamB);
		if(pointsA < 0)
			throw new IllegalArgumentException("pointsA must not be less than 0.");
		if(pointsB < 0)
			throw new IllegalArgumentException("pointsB must not be less than 0.");

		this.pointsA = pointsA;
		this.pointsB = pointsB;

		this.extraTime = extraTime;

		if(isFinishable() && isFinished) {
			setFinished(true);
		}
	}

	/**
	 * Creates a new match between teamA and teamB with given points for each team.
	 * The match will be marked as finished if the scoreCalculator does allow it.
	 * @param scoreCalculator
	 * @param teamA
	 * @param teamB
	 * @param pointsA
	 * @param pointsB
	 */
	public Match(ScoreCalculator scoreCalculator, Team teamA, Team teamB, int pointsA, int pointsB, boolean extraTime) {
		this(scoreCalculator, teamA, teamB, pointsA, pointsB, extraTime, true);
	}

	/**
	 * Creates a new match between teamA and teamB.
	 * Both teams will get 0 points and the match will be marked as not finished.
	 * @param scoreCalculator
	 * @param teamA
	 * @param teamB
	 */
	public Match(ScoreCalculator scoreCalculator, Team teamA, Team teamB) {
		if(scoreCalculator == null)
			throw new IllegalArgumentException("scoreCalculator must not be null.");
		if(teamA == null)
			throw new IllegalArgumentException("teamA must not be null.");
		if(teamB == null)
			throw new IllegalArgumentException("teamB must not be null.");
		if(teamA == teamB)
			throw new IllegalArgumentException("teamA and teamB are equal.");

		this.scoreCalculator = scoreCalculator;
		this.teamA = teamA;
		this.teamB = teamB;

		pointsA = 0;
		pointsB = 0;

		isFinished = false;
		extraTime = false;
	}

	/**
	 * Returns true, if this match is finished.
	 * A match cannot be finished, if both teams have the same amount of points.
	 * @return
	 */
	public boolean isFinished() {
		return isFinished;
	}

	public boolean extraTime() {
		return extraTime;
	}

	public void setExtraTime(boolean extraTime) {
		this.extraTime = extraTime;
	}

	/**
	 * Checks if both teams have a different amount of points.
	 * @return
	 */
	public boolean isFinishable() {
		return scoreCalculator.isFinishable(pointsA, pointsB);
	}

	/**
	 * Marks current match as finished or unfinished.
	 * Side effects: updates team stats of teams.
	 * Marking a match as finished is only allowed it the score calculator does allow it.
	 */
	public void setFinished(boolean finished) {
		if(isFinished == finished)
			return;

		if(finished) {
			if(!isFinishable())
				throw new RuntimeException("The match is not finishable with current score calculator.");
			isFinished = true;
		}
		else {
			isFinished = false;
		}

		// update team stats
		updateTeamStats();
	}

	public void finish() {
		setFinished(true);
	}

	public void unfinish() {
		setFinished(false);
	}

	/**
	 * Delivers the winning team of a finished match.
	 * @return
	 */
	public Team getWinner() {
		if(isDrawn())
			return null;
		return pointsA > pointsB ? teamA : teamB;
	}

	public boolean isDrawn() {
		if(!isFinished())
			throw new RuntimeException("The match is not finished.");
		return pointsA == pointsB;
	}

	/**
	 * Delivers the losing team of a finished match.
	 * @return
	 */
	public Team getLoser() {
		return teamA == getWinner() ? teamB : teamA;
	}

	public Team getTeamA() {
		return teamA;
	}

	public Team getTeamB() {
		return teamB;
	}

	public int getPointsForTeam(Team team) {
		if(teamA == team)
			return pointsA;
		if(teamB == team)
			return pointsB;
		throw new IllegalArgumentException("Given team not in match.");
	}

	public int getPointsForOpponent(Team team) {
		return getPointsForTeam(team) == pointsA ? pointsB : pointsA;
	}

	public int getPointsA() {
		return pointsA;
	}

	public int getPointsB() {
		return pointsB;
	}

	public void setPointsA(int pointsA) {
		setPoints(pointsA, pointsB);
	}

	public void setPointsB(int pointsB) {
		setPoints(pointsA, pointsB);
	}

	public void setPoints(int pointsA, int pointsB) {
		if(isFinished())
			throw new RuntimeException("Match already finished.");

		this.pointsA = pointsA;
		this.pointsB = pointsB;
	}

	public int getScoreForTeam(Team team) {
		if(teamA == team)
			return getScoreA();
		if(teamB == team)
			return getScoreB();
		throw new IllegalArgumentException("Given team not in match.");
	}
	public int getScoreForOpponent(Team team) {
		return getScoreForTeam(getOpponent(team));
	}
	public int getScoreA() {
		return scoreCalculator.getScoreForTeamA(pointsA, pointsB);
	}
	public int getScoreB() {
		return scoreCalculator.getScoreForTeamB(pointsA, pointsB);
	}

	public boolean containsTeam(Team team) {
		return teamA == team || teamB == team;
	}

	public Team getOpponent(Team team) {
		if(teamA == team)
			return teamB;
		if(teamB == team)
			return teamA;
		throw new IllegalArgumentException("Given team not in match.");
	}

	/**
	 * Convenience method for {@link TeamStats#updateTeamStats(Match)} and {@link TeamStats#revertTeamStats(Match)}
	 */
	private void updateTeamStats() {
		if(isFinished) {
			TeamStats.updateTeamStats(this);
		}
		else {
			// a little bit HACKish: temporary lock match. => no exception from getters.
			isFinished = true;
			TeamStats.revertTeamStats(this);
			isFinished = false;
		}
	}

	public boolean isRecalculatable() {
		return !isFinished && pointsA == pointsB && pointsA == 0;
	}

	@Override
	public String toString() {
		return teamA + " - " + teamB + " (" + pointsA + ":" + pointsB + (isFinished ? " - finished" : "") + ")";
	}

	public ScoreCalculator getScoreCalculator() {
		return scoreCalculator;
	}

}
